Skip to content

Make build_appimage.sh fail fast with clear, actionable errors#63

Draft
JRufer wants to merge 8 commits into
masterfrom
claude/build-appimage-failures-snz8v9
Draft

Make build_appimage.sh fail fast with clear, actionable errors#63
JRufer wants to merge 8 commits into
masterfrom
claude/build-appimage-failures-snz8v9

Conversation

@JRufer

@JRufer JRufer commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Problem

build_appimage.sh was failing for reasons the script never surfaced clearly — each failure showed up as a cryptic message deep into (or at the very end of) a long build. Working through a real failure end-to-end, the blocker turned out to be the AppImage packaging step:

[gtk/stdout] Deploying shared library /usr/lib/insync/libgio-2.0.so.0
[gtk/stdout] ERROR: Could not find dependency: libselinux.so.1
[gtk/stdout] ERROR: Failed to deploy library:  /usr/lib/insync/libgio-2.0.so.0
ERROR: Failed to run plugin: gtk (exit code: 1)
failed to bundle project `failed to run linuxdeploy`

Root cause: linuxdeploy's GTK plugin deploys the GLib/GTK stack by globbing library names across /usr/lib and its subdirectories (not via the loader, the ld.so cache, or the environment). Insync ships its own libgio-2.0.so.0 / libgobject / libgdk_pixbuf under /usr/lib/insync/, so the plugin sweeps those in and then fails because Insync's libgio needs libselinux.so.1, which isn't installed on Arch/CachyOS. .deb/.rpm don't deploy libraries, so only the AppImage target broke — and Tauri collapsed it all into a bare "failed to run linuxdeploy". (The glob nature was confirmed when linuxdeploy deployed a stray libgdk_pixbuf-2.0.so.0.bak file — only a filesystem glob would match that.)

The same investigation surfaced several other silent-failure gaps in the script's preflight.

Changes (build_appimage.sh + install.sh, additive — default behavior unchanged)

  • Detect shadow GLib/GTK libraries under /usr/lib — scan the library prefixes and their subdirectories (skipping the multiarch dir) for third-party copies of libgio/libgobject/libglib/libgdk_pixbuf/libgtk-3, mirroring what the GTK plugin actually does. When found (the Insync case), warn up front with the correct fix: move the directory out of /usr/lib for the build, then restore it. (Renaming it in place or renaming the .so files does not work — the glob still matches anything under /usr/lib.)
  • --verbose / -v — pass --verbose to tauri build and set VERBOSE=2 so linuxdeploy's real error surfaces instead of Tauri's generic wrapper message. (This is what made the diagnosis possible.)
  • patchelf/file preflightlinuxdeploy shells out to these; check them up front and add them to every install.sh build-dependency line (and to its "missing build tools" detection).
  • GTK/WebKit dev-library preflightpkg-config gtk+-3.0 / webkit2gtk-4.1, so a missing dev package fails clearly instead of as an opaque gdk-3.0 not found mid-compile.
  • ONNX host reachability probe + --no-moonshine / MOONSHINE=0 — the mandatory Moonshine feature downloads ONNX Runtime from cdn.pyke.io at build time; probe it before the long compile, and add an escape hatch to build the whisper-cpp-only AppImage offline. Moonshine stays on by default.

Testing

  • bash -n passes for both scripts.
  • Verified the shadow-lib detector flags a simulated /usr/lib/insync (including a stray .bak) while correctly skipping the multiarch dir and legitimate gio/modules.
  • Verified all four cuda×no-moonshine feature combinations produce the correct --features string.
  • Confirmed the ONNX reachability probe detects an unreachable host and prints the --no-moonshine guidance before compiling.
  • Reproduced the compile end-to-end (frontend → overlay → main app) to confirm the script's own stages run correctly; the failures were all in downstream tool/library resolution, which these preflights now surface up front.

Note: linuxdeploy itself couldn't be run in the CI sandbox (egress policy blocks the CDN it downloads from), so the detector and --verbose switch are validated by syntax checks + simulated-input tests and by the real failure logs from the reporting machine, rather than a full end-to-end AppImage build here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU

claude added 4 commits July 6, 2026 18:36
…ompile failures

Since Moonshine became a mandatory feature in every AppImage build, the
compile now downloads a prebuilt ONNX Runtime from cdn.pyke.io at build
time. When that download is unreachable (offline, firewall, proxy/egress
policy, or a yanked CDN artifact) the entire build dies ~15 minutes into
compilation with an opaque ort-sys error. A machine missing the GTK/WebKit
dev libraries fails the same way with a cryptic "gdk-3.0 not found".

Make the script fail fast and actionably instead:

- Add a GTK/WebKit development-library preflight (pkg-config gtk+-3.0 and
  webkit2gtk-4.1) alongside the existing unsquashfs/npm/cargo/cmake checks,
  with per-distro install hints.
- Probe cdn.pyke.io reachability before the long compile when Moonshine is
  enabled, and warn with the offline escape hatch if it is blocked.
- Add a --no-moonshine flag (and MOONSHINE=0 env) to build the whisper-cpp
  -only AppImage without the build-time ONNX Runtime download, restoring the
  pre-Moonshine offline-capable build. Moonshine stays on by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
…deploy

The final packaging step runs Tauri's AppImage bundler, which downloads and
executes `linuxdeploy`. linuxdeploy shells out to `patchelf` (to rewrite
library rpaths) and `file` (to classify binaries). When either is missing the
Rust compile succeeds but bundling dies late with a generic
"failed to run linuxdeploy" and no obvious cause. Neither tool was installed
or checked anywhere, even though cmake/npm/etc. were.

- build_appimage.sh: add a patchelf/file preflight to the toolchain checks
  with per-distro install hints.
- install.sh: add patchelf and file to every build-dependency install line,
  and treat their absence as a missing build tool so the installer pulls them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
Tauri collapses any AppImage packaging failure into a bare
"failed to run linuxdeploy", hiding linuxdeploy's own diagnostics. Add a
--verbose/-v flag that passes --verbose to `tauri build` and sets VERBOSE=2 so
linuxdeploy logs at debug level, making the underlying cause (FUSE, a GTK
plugin, an unresolved library, etc.) visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
The AppImage target fails at the linuxdeploy step ("failed to run linuxdeploy")
when a third-party app registers its own copy of core GLib libraries on the
system linker path via /etc/ld.so.conf.d. linuxdeploy then resolves the app's
libgio/libglib to those shadow copies and fails to bundle them because they
pull in dependencies the host lacks. The common culprit is Insync:
/usr/lib/insync/libgio-2.0.so.0 needs libselinux.so.1, absent on Arch/CachyOS,
so the GTK plugin aborts. deb/rpm don't deploy libraries, which is why only the
AppImage build breaks.

Scan /etc/ld.so.conf.d entries during the toolchain preflight and, when a
non-standard directory ships libgio-2.0/libglib-2.0, warn up front with the
exact remediation (temporarily disable the .conf, run ldconfig, build, restore)
instead of letting the build fail cryptically 20 minutes in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
@JRufer JRufer changed the title Fail AppImage builds fast with clear errors Make build_appimage.sh fail fast with clear, actionable errors Jul 7, 2026
claude added 4 commits July 7, 2026 20:18
…tk plugin

The previous check looked at /etc/ld.so.conf.d, but that is the wrong mechanism:
linuxdeploy's GTK plugin deploys the GLib/GTK stack by globbing library names
across /usr/lib and its subdirectories — not via the loader, ld.so cache, or
environment. It therefore sweeps in any third-party copy it finds (e.g. Insync's
/usr/lib/insync/libgio-2.0.so.0) and fails to bundle it when it needs a library
the host lacks (libselinux.so.1 on Arch/CachyOS), surfacing as a late, cryptic
"failed to run linuxdeploy". Confirmed by linuxdeploy deploying a stray
"libgdk_pixbuf-2.0.so.0.bak" file — only a filesystem glob would match that.

Replace the ld.so.conf.d scan with one that mirrors the plugin: look for
libgio/libgobject/libglib/libgdk_pixbuf/libgtk-3 copies in non-standard
subdirectories of the library prefixes (skipping the multiarch dir), and, when
found, warn up front with the correct fix — move the directory OUT of /usr/lib
for the build (renaming it in place or renaming the .so files does not work,
since the glob still matches anything under /usr/lib).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
…the build

The shadow-library problem (Insync's /usr/lib/insync breaking linuxdeploy's gtk
plugin) previously required moving the directory out of /usr/lib by hand before
every build and restoring it after — fiddly and easy to get wrong. Automate it.

With --hide-shadow-libs (needs sudo), each detected shadow directory is moved to
a sibling path outside the scanned prefix (e.g. /usr/lib/insync ->
/usr/insync.buildhide) just before `tauri build`, and an EXIT/INT/TERM trap
restores every moved directory when the build finishes or fails, so the
third-party app is never left displaced. Without the flag the behavior is
unchanged: the script only warns and now also points at the flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
…ision)

On Arch/CachyOS /usr/lib64 (and often /lib) is a symlink to /usr/lib, so the
scan found the same physical directory twice — e.g. /usr/lib/insync and
/usr/lib64/insync. With --hide-shadow-libs the first was moved to
/usr/insync.buildhide and the second collided with that path, aborting the build
(the trap then restored the first). Canonicalize each candidate with readlink -f
and dedupe so each physical directory is processed once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
Previously the shadow-library workaround (moving Insync's /usr/lib/insync out of
linuxdeploy's search path) required the --hide-shadow-libs flag; a plain
`./build_appimage.sh` only warned and then failed at linuxdeploy. Make hiding the
default whenever a shadow directory is actually detected — it is a safe,
self-restoring operation (EXIT trap puts everything back) that only runs when
needed. Add --keep-shadow-libs / KEEP_SHADOW_LIBS=1 to opt out for anyone who
does not want the script to touch system directories. Users without such libs
are unaffected: nothing is detected, so nothing is moved and no sudo is used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants